package com.xishu.foodpanda;


import com.xishu.config.Config;
import com.xishu.config.Constant;
import com.xishu.entity.NameAndId;
import com.xishu.entity.foodpanda.Category;
import com.xishu.entity.foodpanda.Menu;
import com.xishu.entity.foodpanda.Product;
import com.xishu.entity.shop.*;
import com.xishu.exception.ResponseStatusException;
import com.xishu.response.ResponseStatus;
import com.xishu.response.VerifyUtil;
import com.xishu.service.CommonService;
import com.xishu.service.ItemService;
import com.xishu.service.ObjectService;
import com.xishu.service.ShopService;
import com.xishu.to.foodpanda.*;
import com.xishu.util.DateUtil;
import com.xishu.util.Tools;
import net.sf.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.stream.Collectors;

public class FoodPandaImportService implements Tools {
    public static final String TOPPING = "Topping";
    public static final String PRODUCT = "Product";
    private static Logger logger = LoggerFactory.getLogger(FoodPandaImportService.class);

    private static FoodPandaImportService instance = new FoodPandaImportService();
    private FoodPandaClient foodPandaClient = FoodPandaClient.getInstance();
    private ItemService itemService = ItemService.getInstance();

    private CommonService commonService = CommonService.getInstance();

    private ObjectService objectService = ObjectService.getInstance();
    private FoodPandaConfigService foodPandaConfigService = FoodPandaConfigService.getInstance();

    private Config config = Config.getInstance();

    private FoodPandaImportService() {
    }

    public static FoodPandaImportService getInstance() {
        return instance;
    }

    /**
     * 分店登录foodpanda,如果是成功，会返回token,然后用token去做场景，品类，菜品的数据同步
     * token的有效期是30分钟
     *
     * @param shop
     */
    public String login(Shop shop) throws ResponseStatusException {
        String loginResult = foodPandaClient.login(shop);
        VerifyUtil.verify(() -> isNotEmpty(loginResult));
        JSONObject jsonObject = JSONObject.fromObject(loginResult);
        VerifyUtil.verify(() -> jsonObject.containsKey("access_token"), ResponseStatus.USER_NAME_OR_PASSWORD_WRONG);

        return jsonObject.getString("access_token");
    }

    /**
     * 生成一个分店的json数据
     * <p>
     * json数据是由map转化而生成的
     *
     * @param shopId
     */
    public Map generateShopToMap(Long shopId) throws Exception {
        Map<String, Object> map = new LinkedHashMap<>();

        //先将所有菜的图标，都写到map里面
        List<Product> productList = findProductListByShopId(shopId);
        addImgListToMap(map, productList);

        //生成每一个menu里面的数据，首先生成product的数据，然后生成category的数据，最后生成Menu里面的数据
        List<Menu> menuList = foodPandaConfigService.findMenuList(shopId);
        for (Menu menu : menuList) {
            map.putAll(menuToMap(menu));
        }

        //在外层添加catalog以及items
        Map<String, Object> itemsMap = new LinkedHashMap<>();
        itemsMap.put("items", map);

        Map<String, Object> foodpandaMap = new LinkedHashMap<>();
        foodpandaMap.put("catalog", itemsMap);

        Shop shop = ShopService.getInstance().findShopById(shopId);
        foodpandaMap.put("vendors", Arrays.asList(shop.getFoodPandaConfig().getVendors()));

        return foodpandaMap;
    }

    /**
     * 将一个menu转化成一个map集合，拿到map集合之后需要做json化
     * 里面需要包含各个category以及schedule
     *
     * @param menu
     * @return
     */
    public Map<String, Object> menuToMap(Menu menu) throws Exception {
        Map<String, Object> map = new LinkedHashMap<>();

        //先将该menu下面的所有菜均查询出来,将菜的imgId先写好
        List<Product> productList = findProductListByMenuId(menu.getShopId(), menu.getId());

        validPrice(productList);

        //按照Product先生成
        List<Map> productListMap = productList.stream().map(product -> {
            try {
                return productToIdMap(product, menu.getId());
            } catch (Exception e) {
                logger.error("e", e);
                return null;
            }
        }).filter(p -> p != null).collect(Collectors.toList());

        productListMap.forEach(productMap -> {
            map.putAll(productMap);
        });

        //生成topping product
        List<Map> toppingProductList = productList.stream().map(product -> {
            try {
                return new ProductChain().toToppingProductMap(product, menu.getId());
            } catch (Exception e) {
                logger.error("e", e);
                return null;
            }
        }).filter(p -> p != null).collect(Collectors.toList());

        toppingProductList.forEach(productMap -> {
            map.putAll(productMap);
        });

        //生成setMenu里面的菜的topping product
        List<Map> setMenuToppingProductList = productList.stream().map(product -> {
            try {
                return new ProductChain().toSetMenuToppingProductMap(product, menu.getId());
            } catch (Exception e) {
                logger.error("e", e);
                return null;
            }
        }).filter(p -> p != null).collect(Collectors.toList());

        setMenuToppingProductList.forEach(productMap -> {
            map.putAll(productMap);
        });

        //套餐里面的固定菜下面的菜包含细节的，生成固定菜
        List<Map> setMenuFixItemDetailProductList = productList.stream().map(product -> {
            try {
                return new ProductChain().toSetMenuFixItemDetailProductMap(product);
            } catch (Exception e) {
                logger.error("e", e);
                return null;
            }
        }).filter(p -> p != null).collect(Collectors.toList());

        setMenuFixItemDetailProductList.forEach(productMap -> {
            map.putAll(productMap);
        });

        //再生成category
        List<Category> categoryList = findCategoryListByMenuId(menu.getShopId(), menu.getId());
        List<Map> categoryListMap = categoryList.stream().map(category -> {
            try {
                return categoryToMap(category, menu.getId());
            } catch (Exception e) {
                logger.error("e", e);
                return null;
            }
        }).filter(p -> p != null).collect(Collectors.toList());
        categoryListMap.forEach(categoryMap -> {
            map.putAll(categoryMap);
        });

        //生成topping级别的category,普通菜以及套餐都放在一起的
        List<Map> toppingCategoryList = categoryList.stream().map(category -> {
            try {
                return categoryToppingToMap(category);
            } catch (Exception e) {
                logger.error("e", e);
                return null;
            }
        }).filter(p -> p != null).collect(Collectors.toList());
        toppingCategoryList.forEach(categoryMap -> {
            map.putAll(categoryMap);
        });

        //定义topping，套餐也包含在一起的
        List<Map> toppings = productList.stream().map(product -> {
            try {
                return new ProductChain().toToppingMap(product, menu.id);
            } catch (Exception e) {
                logger.error("e", e);
                return null;
            }
        }).filter(p -> p != null).collect(Collectors.toList());

        toppings.forEach(productMap -> {
            map.putAll(productMap);
        });

        //定义variant
        List<Map> variantMap = productList.stream().map(product -> {
            try {
                return new ProductChain().toVariantMap(product, menu.id);
            } catch (Exception e) {
                logger.error("e", e);
                return null;
            }
        }).filter(p -> p != null).collect(Collectors.toList());

        variantMap.forEach(productMap -> {
            map.putAll(productMap);
        });

        //构造schedule
        Map<String, Object> scheduleMap = menuToScheduleMap(menu);
        map.putAll(scheduleMap);

        //再构造menu
        FoodPandaMenu foodPandaMenu = new FoodPandaMenu();
        foodPandaMenu.setId("menu." + menu.getId());
        //写类型
        if (getInt(menu.getMenuType()) == Constant.FOOD_PANDA_MENU_TYPE_DELIVERY) {
            foodPandaMenu.setMenuType("DELIVERY");
        } else {
            foodPandaMenu.setMenuType("PICK_UP");
        }

        foodPandaMenu.setTitle(new FoodPandaZhEn(menu.getName_zh(), menu.getName_en()));
        foodPandaMenu.setSchedule(new FoodPandaBase(generateScheduleId(menu), "ScheduleEntry"));
        List<FoodPandaBase> productListInMenu = productList.stream().map(product -> {
            return new FoodPandaBase(FoodPandaBaseService.generateProductId(menu.getId(), product.getCategoryId(), product.getId()), PRODUCT);
        }).collect(Collectors.toList());
        foodPandaMenu.setProducts(productListInMenu);
        map.putAll(FoodPandaBaseService.addIdMap(foodPandaMenu));

        return map;
    }

    private void validPrice(List<Product> productList) throws ResponseStatusException {
        List<Product> notConfigPriceProductList = new ArrayList<>();

        //依次校验价格是否有配置
        productList.forEach(product -> {
            List<PriceConfig> priceConfigList = product.getPriceConfigList().stream().filter(priceConfig -> {
                return priceConfig.getPrice() == null;
            }).collect(Collectors.toList());

            if (isNotEmpty(priceConfigList)) {
                notConfigPriceProductList.add(product);
            }
        });

        Object[] productNameZhArray = notConfigPriceProductList.stream().map(product -> product.getName_zh()).toArray();
        Object[] productNameEnArray = notConfigPriceProductList.stream().map(product -> product.getName_en()).toArray();

        //调试期间的开关
        if (config.configFoodPandaPrice()) {
            VerifyUtil.verifyZhEn(() -> isEmpty(notConfigPriceProductList), ResponseStatus.PRODUCT_CONFIG_NOT_CONFIG, productNameZhArray, productNameEnArray);
        }
    }


    /**
     * 将图片添加到map里面
     *
     * @param map
     * @param productList
     */
    public void addImgListToMap(Map<String, Object> map, List<Product> productList) {
        List<FoodPandaImg> foodPandaImgList = productList.stream().map(product -> generateImgFromProduct(product)).filter(foodPandaImg -> foodPandaImg != null).collect(Collectors.toList());
        foodPandaImgList.stream().forEach(foodPandaImg -> {
            try {
                map.put(foodPandaImg.getId(), FoodPandaBaseService.beanToMap(foodPandaImg));
            } catch (Exception e) {
                //有异常的数据暂时不写
                logger.error("e", e);
            }
        });
    }

    /**
     * 将一个Category转化成一个map集合
     * 里面需要包含各种菜
     *
     * @param category
     * @return
     */
    public Map<String, Object> categoryToMap(Category category, Long menuId) throws Exception {
        FoodPandaCategory foodPandaCategory = new FoodPandaCategory();
        foodPandaCategory.setId(String.format("menu.%s.category.%s", menuId, category.getId()));
        foodPandaCategory.setTitle(new FoodPandaZhEn(category.getName_zh(), category.getName_en()));
        foodPandaCategory.setOrder(category.getShowOrder());

        if (isNotEmpty(category.getDescription_zh()) || isNotEmpty(category.getDescription_en())) {
            foodPandaCategory.setDescription(new FoodPandaZhEn(category.getDescription_zh(), category.getDescription_en()));
        } else {
            logger.info("do not has the description in category {}", category.getId());
        }

        List<Product> productList = findProductListByCategoryId(category.getId());
        List<FoodPandaBase> foodPandaProductList = productList.stream().map(product -> {
            FoodPandaOrder foodPandaOrder = new FoodPandaOrder(FoodPandaBaseService.generateProductId(menuId, category.getId(), product.getId()), PRODUCT, product.getShowOrder());
            return foodPandaOrder;
        }).collect(Collectors.toList());

        foodPandaCategory.setProducts(foodPandaProductList);

        return FoodPandaBaseService.addIdMap(foodPandaCategory);
    }

    /**
     * 如果category下面的菜有细节，那么这些细节单独组成一个category
     *
     * @param category
     * @return
     */
    public Map<String, Object> categoryToppingToMap(Category category) throws Exception {
        List<Product> productList = findProductListByCategoryId(category.getId());
        ArrayList<FoodPandaBase> foodPandaProductList = new ArrayList<>();

        //如果菜下面有细节，细节也需要加入到category下面
        List<ItemDetail> itemDetailList = productList.stream().filter(product -> {
            return FoodPandaBaseService.isCommonProductHasDetail(product);
        }).flatMap(product -> {
            return product.getItemDetailList().stream();
        }).collect(Collectors.toList());

        itemDetailList.stream().forEach(itemDetail -> {
            itemDetail.getNameAndPriceList().forEach(nameAndPrice -> {
                foodPandaProductList.add(new FoodPandaBase(FoodPandaBaseService.generateToppingProductId(itemDetail.getId(), nameAndPrice.getId()), PRODUCT));
            });
        });

        //将套餐下面的菜，添加到category下面
        List<SetMenuOneItem> setMenuOneItemList = productList.stream().filter(product -> getBoolean(product.getSetMenu())).flatMap(product -> product.getSetMenuOneItemList().stream()).collect(Collectors.toList());

        setMenuOneItemList.forEach(setMenuOneItem -> {
            //非固定菜的处理
            if (!getBoolean(setMenuOneItem.getFixed())) {
                setMenuOneItem.getItemList().forEach(item -> {
                    foodPandaProductList.add(new FoodPandaBase(FoodPandaBaseService.generateSetMenuToppingProductId(setMenuOneItem.getId(), item.getId()), PRODUCT));
                });
            } else {
                //固定菜的处理，有几个就生成几个
                for (Integer i = 0; i < setMenuOneItem.getItemNumber(); i++) {
                    Item item = setMenuOneItem.getItemList().get(0);
                    foodPandaProductList.add(new FoodPandaBase(FoodPandaBaseService.generateSetMenuToppingProductId(setMenuOneItem.getId(), item.getId(), i), PRODUCT));
                }

                //固定菜下面的细节菜，也依次生成
                Item item = FoodPandaBaseService.getSetMenuItem(setMenuOneItem).get();
                if (ItemService.isCommonItemHasDetail(item)) {
                    for (Integer index = 1; index <= setMenuOneItem.getItemNumber(); index++) {
                        for (ItemDetail itemDetail : item.getItemDetailList()) {
                            for (NameAndPrice nameAndPrice : itemDetail.getNameAndPriceList()) {
                                foodPandaProductList.add(new FoodPandaBase(FoodPandaBaseService.generateFixSetMenuItemDetailProductId(setMenuOneItem.getId(), item.getId(), itemDetail.getId(), nameAndPrice.getId(), index), PRODUCT));
                            }
                        }
                    }
                }
            }
        });

        if (isEmpty(foodPandaProductList)) {
            logger.info("do not has topping in the category");
            return new HashMap<>();
        }

        FoodPandaCategory foodPandaCategory = new FoodPandaCategory();
        foodPandaCategory.setId(String.format("category.topping.%s", category.getId()));
        foodPandaCategory.setTitle(new FoodPandaZhEn("Toppings", "Toppings"));
        foodPandaCategory.setDescription(new FoodPandaZhEn("Toppings", "Toppings"));
        foodPandaCategory.setProducts(foodPandaProductList);
        return FoodPandaBaseService.addIdMap(foodPandaCategory);
    }

    /**
     * 将menu转化成schedule的map
     *
     * @param menu
     * @return
     */
    public Map<String, Object> menuToScheduleMap(Menu menu) throws Exception {
        //time config list 的开始和结束时间均是一样的，只是星期几不一样
        List<SceneTimeConfig> timeConfigList = menu.getTimeConfigList();
        if (isEmpty(timeConfigList)) {
            logger.info("timeConfigList is empty in menu {}", menu.getId());
            return new HashMap<>();
        }

        SceneTimeConfig sceneTimeConfig = timeConfigList.get(0);

        //构造schedule
        FoodPandaSchedule foodPandaSchedule = new FoodPandaSchedule();
        foodPandaSchedule.setStartTime(sceneTimeConfig.getStartTime());
        foodPandaSchedule.setEndTime(sceneTimeConfig.getEndTime());
        List<String> wekDayList = timeConfigList.stream().map(p -> p.getDay()).map(day -> {
            try {
                return DateUtil.getWeekDayString(day).toUpperCase();
            } catch (ResponseStatusException e) {
                logger.error("e", e);
                return "";
            }
        }).collect(Collectors.toList());

        foodPandaSchedule.setWeekDays(wekDayList);
        foodPandaSchedule.setId(generateScheduleId(menu));
        return FoodPandaBaseService.addIdMap(foodPandaSchedule);
    }

    /**
     * 生成scheduleId
     *
     * @param menu
     * @return
     */
    private String generateScheduleId(Menu menu) {
        return String.format("menu.%s.schedule", menu.getId());
    }

    /**
     * variants 作为不同的逻辑选项，当前不使用。
     * <p>
     * <p>
     * 1、如果是普通菜，普通菜下面的细节的定义都放在items下面,并且当成Topping,
     * 1.1 细节下面的每一个名称，比如大份，小份，均当成一个Product来处理，并且放在products下面
     * 1.2 每一个Topping需要放在
     *
     * @param product
     * @param menuId
     * @return
     * @throws Exception
     */
    public Map productToIdMap(Product product, Long menuId) throws Exception {
        return new ProductChain().toMap(product, menuId);
    }

    /**
     * 导入Menu数据
     *
     * @param shop
     * @return
     */
    public void importCatalogList(Shop shop) throws Exception {
        VerifyUtil.verify(() -> shop != null);
        VerifyUtil.verify(() -> shop.getFoodPandaConfig() != null);
        FoodPandaConfig foodPandaConfig = shop.getFoodPandaConfig();
        VerifyUtil.verify(() -> getBoolean(foodPandaConfig.getConfigComplete()));
        String token = login(shop);
        Map dataMap = generateShopToMap(shop.getId());

//        logger.info("data map is {}", JSON.toJSONString(dataMap));
        if (config.sendToFoodPanda()) {
            foodPandaClient.sendCmd(String.format("/v2/chains/%s/catalog", foodPandaConfig.getChainCode()), dataMap, token);
        }
    }

    /**
     * 通过menu id来查询category 集合
     *
     * @param menuId
     * @return
     */
    public List<Category> findCategoryListByMenuId(Long shopId, Long menuId) {
        if (shopId == null) {
            logger.info("shop id is empty");
            return new ArrayList<>();
        }

        if (menuId == null) {
            logger.info("menu id is empty");
            return new ArrayList<>();
        }

        Category category = new Category();
        category.setShopId(shopId);

        List<Category> categoryList = commonService.searchAll(category);
        if (isEmpty(categoryList)) {
            logger.info("category list is empty");
            return new ArrayList<>();
        }

        List<Category> filterCategoryList = categoryList.stream().filter(categoryTmp -> {
            List<NameAndId> sceneList = categoryTmp.getSceneList();
            if (isEmpty(sceneList)) {
                return false;
            }

            //返回匹配上的menu
            sceneList = sceneList.stream().filter(p -> equals(p.getId(), menuId)).collect(Collectors.toList());
            return isNotEmpty(sceneList);
        }).collect(Collectors.toList());

        //再排个序
        filterCategoryList.sort(Comparator.comparing(Category::getShowOrder));

        return filterCategoryList;
    }

    /**
     * 查询category 下面所有的菜
     *
     * @param categoryId
     * @return
     */
    public List<Product> findProductListByCategoryId(Long categoryId) {
        Product product = new Product();

        product.setCategoryId(categoryId);

        List<Product> productList = commonService.searchAll(product);
        productList.sort((o1, o2) -> getInt(o1.getShowOrder()) - getInt(o2.getShowOrder()));
        for (Product productPrint : productList) {
            logger.info("findProductListByCategoryId name is {}, show order is {}", productPrint.getName(), productPrint.getShowOrder());
        }

        return productList;
    }

    /**
     * 通过menuId来查询所有的 product
     *
     * @param shopId
     * @param menuId
     * @return
     */
    public List<Product> findProductListByMenuId(Long shopId, Long menuId) {
        List<Category> categoryList = findCategoryListByMenuId(shopId, menuId);
        return categoryList.stream().flatMap(category -> findProductListByCategoryId(category.getId()).stream()).collect(Collectors.toList());
    }

    /**
     * 通过分店ID查询下面所有的菜
     *
     * @param shopId
     * @return
     */
    public List<Product> findProductListByShopId(Long shopId) {
        Product product = new Product();
        product.setShopId(shopId);
        return commonService.searchAll(product);
    }

    /**
     * 从菜品当中产生图片
     * <p>
     * product当中imgId从Item当中获取，而imgId本身是全局唯一的
     *
     * @param product
     * @return
     */
    public FoodPandaImg generateImgFromProduct(Product product) {
        if (isEmpty(product.getImgId())) {
            logger.info("product {} , item id {} do not has img", product.getId(), product.getBelongToItemId());
            return null;
        }

        FoodPandaImg foodPandaImg = new FoodPandaImg();
        foodPandaImg.setUrl(objectService.getImgUrl(product.getImgId()));
        foodPandaImg.setId(product.getImgId());
        return foodPandaImg;
    }

    /**
     * 一個菜品对应foodpanda里面有哪些菜品
     *
     * @param itemId
     * @return
     * @throws Exception
     */
    public void itemAvailable(Long itemId) throws Exception {
        if (!config.foodpandaItemAvailable()) {
            logger.info("foodpandaItemAvailable not open");
            return;
        }

        logger.info("itemAvailable {}", itemId);
        Item item = itemService.findItem(itemId);
        Shop shop = ShopService.getInstance().findShopById(item.getShopId());

        Boolean configComplete = shop.getFoodPandaConfig().getConfigComplete();
        if (!getBoolean(configComplete)) {
            logger.info("shop {} , food panda not config complete", shop.getId());
            return;
        }

        List<Product> relatedProductList = foodPandaConfigService.getAllItemLevelProduct(item.getId());
        //每一个菜的ID，需要转化成在foodpanda里面的对应的一个ID
        List<String> relatedProductIdList = relatedProductList.stream().flatMap(product -> {
            try {
                Long categoryId = product.getCategoryId();
                Category category = foodPandaConfigService.findCategoryById(categoryId);
                return category.getSceneList().stream().map(nameAndId -> {
                    Long menuId = nameAndId.getId();
                    return FoodPandaBaseService.generateProductId(menuId, categoryId, product.id);
                });
            } catch (Exception e) {
                logger.error("e", e);
                return new ArrayList<String>().stream();
            }
        }).filter(p -> p != null).collect(Collectors.toList());

        //如果菜品售罄了，那么将菜的状态修改成不可用
        //如果菜品还有，查看是否在线，在线的时候，那么菜品可用，不在线的时候，菜品不可用
        boolean available = !getBoolean(item.getSaleOut()) && getBoolean(item.getOnline());

        itemAvailable(shop, relatedProductIdList, "ITEM", available);

        List<String> toppingLevelIdList = new ArrayList<>();
        //处理topping级别的菜
        foodPandaConfigService.getAllToppingLevelProduct(item.getId()).forEach(product -> {
            product.getSetMenuOneItemList().forEach(setMenuOneItem -> {
                setMenuOneItem.getItemList().forEach(itemTmp -> {
                    if (equals(itemTmp.getId(), itemId)) {
                        toppingLevelIdList.add(FoodPandaBaseService.generateSetMenuToppingProductId(setMenuOneItem.getId(), itemId));
                    }
                });
            });
        });
        itemAvailable(shop, toppingLevelIdList, "TOPPING", available);
    }

    /**
     * 菜品的状态同步至foodpanda
     *
     * @param shop
     * @param productIdList
     * @throws Exception
     */
    public void itemAvailable(Shop shop, List<String> productIdList, String type, boolean available) throws Exception {
        VerifyUtil.verify(() -> shop != null);
        VerifyUtil.verify(() -> shop.getFoodPandaConfig() != null);
        FoodPandaConfig foodPandaConfig = shop.getFoodPandaConfig();
        VerifyUtil.verify(() -> getBoolean(foodPandaConfig.getConfigComplete()));

        if (isEmpty(productIdList)) {
            logger.info("product id list is empty, {}", type);
            return;
        }

        String token = login(shop);

        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("type", type);
        dataMap.put("items", productIdList);
        dataMap.put("isAvailable", available);

        foodPandaClient.sendCmd(String.format("/v2/chains/%s/vendors/%s/catalog/items/availability", foodPandaConfig.getChainCode(), foodPandaConfig.getVendors()), dataMap, token);
    }

}


